#include <stdlib.h>
#include <time.h>
#include <stdio.h>
                /*全局变量*/
/*
 记录表格状态
 id    当前位周围雷数
 state 两种状态：'*'当前位置未处理/'F'已插上小红旗/' '当前位不为雷
 */
typedef  struct {
    int id;
    char state;
}Table;
//难度(方阵的n)
static int degree;
//总雷数
static int num_Mine;
//当前位置是否存在地雷 :0不存在/1存在
static int Mine_Exist;
//操作的位置和步骤
static int row,list;
//当前位置周围小红旗的数量
static int num_Flag;
//当前位置周围没有被小红旗覆盖的雷的数量
static int num_MineLift;
//生命周期
static int live;
static clock_t begin;
static clock_t end;
                /*函数声明*/
//显示提示信息
void help();
//开始程序
void start();
//用户输入操作位置和步骤
int inputExecute();
//初始化数组
void setupData(int mine_Arr[][degree+2],Table show_Arr[degree+2][degree+2]);
//创建MineArr数组
void SetupMineArr(int arr[][degree+2]);
//创建show_Arr数组
void setupShow_Arr(Table show_Arr[degree+2][degree+2]);
//1.点击操作
void click(int row,int list,int mine_Arr[degree+2][degree+2],Table show_Arr[degree+2][degree+2]);
//检查当前位置是否为雷 返回 1（是）/0（否）
int checkMine(int row,int list,int mine_Arr[degree+2][degree+2]);
//检查周边九宫格里面是否有雷，返回雷的个数
int checkAround(int row,int list,int mine_Arr[degree+2][degree+2],Table show_Arr[degree+2][degree+2]);
//更新表状态
void reflash(int row,int list,int mine_Arr[degree+2][degree+2],Table show_Arr[degree+2][degree+2]);
//2.放置小红旗
void setFlag(int row,int list,Table show_Arr[degree+2][degree+2]);
//3.撤掉小红旗
void removeFlag(int row,int list,Table show_Arr[degree+2][degree+2]);
//4.向用户展示最新情况
void showCur(int mine_Arr[degree+2][degree+2],Table show_Arr[degree+2][degree+2],int Mine_Exist);
//检查当前位置是否能显示出一片无雷的区域
void checkAround_F(int row,int list,int mine_Arr[degree+2][degree+2],Table show_Arr[degree+2][degree+2]);
//检查是否成功，成功则输出
void sucess(int mine_Arr[degree+2][degree+2],Table show_Arr[degree+2][degree+2]);
//重新开始
void newGame();
//用户输入不合法,防止死循环
void inputError();

//主程序
int main(int argc, const char * argv[]) {
    help();
    return 0;
}
/*函数定义*/
//测试方法
//void showText(Table show_Arr[degree+2][degree+2]){
//    for (int i=1; i<degree+1; i++) {
//        for (int j=1; j<degree+1; j++) {
//            printf("%4c",show_Arr[i][j].state);
//        }
//        printf("\n");
//        printf("\n");
//    }
//}//测试代码
//显示提示信息
void help(){
    Mine_Exist=0;//默认当前为不是雷
    printf("欢迎来到扫雷\n");
    int num=0;
    while (num!=1&&num!=2&&num!=3) {
        printf("请选择难度 1.初级 2.中级 3.高级\n");
        scanf("%d",&num);
        inputError();//储存用户错误输入，防止死循环
    }//while
    /*
     *degree--表格宽
     *num_Mine--地雷数
     */
    switch (num) {
        case 1:
            degree = 9;
            num_Mine=10;
            break;
        case 2:
            degree = 16;
            num_Mine=40;
            break;
        case 3:
            degree = 22;
            num_Mine=99;
            break;
        default:
            break;
    }//switch
    printf("扫雷游戏规则如下\n");
    printf("一、选择你要进行操作的位置\n");
    printf("二、选择你要进行的操作 1.点击 2.放小红旗 3.撤销小红旗 4.检索周边是否有雷 5.重新开始游戏\n");
    int n=0;
    while (n!=1&&n!=2) {
        system("cls");
        printf("准备好了吗？ 1是  2否\n");
        scanf("%d",&n);
        inputError();
    }//while
    switch (n) {
        case 1:
            start();
            break;
        case 2:
            printf("游戏结束\n");
            break;
        default:
            break;
    }//switch
}
//开始程序
void start(){
    printf("游戏开始\n");
    int mine_Arr[degree+2][degree+2];//储存地雷位置
    Table show_Arr[degree+2][degree+2];//储存显示给用户的数据状态
    /*
     *特别提示：关于数组的行列数
     *用户的进行操作的实际表格数是degree*degree
     *但是如果生成degree*degree的表格存储相关数据，边框数据要进行特殊考虑
     *所以这里生成degree+2*degree+2的数组，四边最外层的当做辅助空间，中间储存相关数据
     */
    
    setupData(mine_Arr,show_Arr);//初始化mine_Arr、show_Arr两个数组
    showCur(mine_Arr,show_Arr,Mine_Exist);//显示当前表格状态
    begin=clock();//计时开始
    live=1;//控制循环是否进行
    Mine_Exist=0;//当前位置是否是地雷 1是 0否
    
    //循环进行操作
    while (live) {
        int step=inputExecute();//用户输入，返回要执行的操作所对应的数字
        switch (step) {
            case 1:
                click(row,list,mine_Arr,show_Arr);
                break;
            case 2:
                setFlag(row,list,show_Arr);
                break;
            case 3:
                removeFlag(row,list,show_Arr);
                break;
            case 4:
                checkAround_F(row,list,mine_Arr,show_Arr);
                break;
            case 5:
                newGame();
            default:
                break;
        }
        showCur(mine_Arr,show_Arr,Mine_Exist);//每次操作结束显示最新的表格
    }
    newGame();//循环结束重新开始游戏
}
//用户输入操作位置和步骤
int inputExecute(){
    row =0;//行
    list=0;//列
    //用户输入操作位置，如果超过范围，提示重新输入
    while (row<1||row>degree||list<1||list>degree) {
            printf("请输入你要操作的位置 如五行四列：5-4\n");
            scanf("%d-%d",&row,&list);
        inputError();
    }//while
    
    int step=0;
    //用户输入操作步骤，如果超过范围，提示重新输入
    while (step<1||step>5) {
        printf("请输入你要进行的操作，1.点击 2.放小红旗 3.撤销小红旗 4.检测周边九宫格是否能显示（提示：此操作要在放置小红旗的周边位置进行）5.重新开始游戏，如执行点击操作，请输入：1\n");
        scanf("%d",&step);
        inputError();
    }
    return step;//返回操作对应的数字
}
//初始化数据
void setupData(int mine_Arr[][degree+2],Table show_Arr[][degree+2]){
    //按照用户选择级别的不同，生成对应数量的地雷数，并随机防止在mine_Arr数组中
    SetupMineArr(mine_Arr);
    //初始化状态表格
    setupShow_Arr(show_Arr);
   }
//创建SetupMineArr数组
void SetupMineArr(int arr[][degree+2]){
    srand(time(NULL));
    int Ran[num_Mine];//储存随机数
    Ran[0]=rand()%(degree*degree);
    int i=1;
    /*
     *生成（num_Mine）个互不相同的随机数（num_Mine代表总雷数）
     *随机数范围 ：0~degree*degree-1
     *该数字代表地雷的放置位置
     */
    while ( i<num_Mine ) {
        int temp=0;
        int ran_num=rand()%(degree*degree);
//        printf("%5d\n",ran_num);
        //判断之前是否随机生成过该数字
        for (int j=0; j<i; j++) {
            if (Ran[j]==ran_num) {
                temp++;
            }//if
        }//for
        //之前未生成过该数字，加入到数组中
        if (temp==0) {
            Ran[i]=ran_num;
            i++;
        }//if
    }//while
//    测试代码：输出所有的随机数
//    for (int i=0; i<num_Mine;i++ ) {
//        printf("%5d",Ran[i]);
//    }
//    printf("\n");//测试结束
    for (int i=0; i<degree+2; i++) {
        //初始化数组
        for (int j=0; j<degree+2; j++) {
            arr[i][j]=0;
        }
    }
    for (int i=0; i<num_Mine; i++) {
        //将随机数转换成数组坐标
        int x=Ran[i]/degree+1;
        //(i-1)*degree +(j-1)=ran[k]
        int y=Ran[i]-(x-1)*degree+1;
        arr[x][y]=1;//更新数组，将有雷的位置，值设置为1
    }
    //测试代码：输出雷分布图
//    for (int i=1; i<degree+1; i++) {
//        for (int j=1; j<degree+1; j++) {
//            printf("%4d",arr[i][j]);
//        }
//        printf("\n");
//    }
//    printf("~~~~~测试代码~~~~~~~~~~\n");
    //测试代码结束
}
//初始化show_Arr数组
void setupShow_Arr(Table show_Arr[degree+2][degree+2]){
    //show_Arr赋值
    for (int i=0; i<degree+2; i++) {
        for (int j=0; j<degree+2; j++) {
            if (i==0&&j!=0) {
                //列坐标
                show_Arr[i][j].id=j;
            }else if (j==0&&i!=0){
                //行坐标
                show_Arr[i][j].id=i;
            }else if(i==0&&j==0){
                //原点
                show_Arr[i][j].id=0;
                show_Arr[i][j].state=' ';
            }
            else{
                show_Arr[i][j].state='*';
                show_Arr[i][j].id=0;
            }
        }//for
    }//for
}
//操作1：点击
void click(int row,int list,int mine_Arr[degree+2][degree+2],Table show_Arr[degree+2][degree+2]){
    //如果该位置插上小红旗则无法点击
    if (show_Arr[row][list].state=='F') {
        printf("无效操作\n");
        return;
    }
    Mine_Exist=checkMine(row, list, mine_Arr);//检测当前位置是否是雷
    if (Mine_Exist==1) {//踩雷
        printf("踩雷啦，再接再励\n");
        live=0;
    }else{
        //更新表信息
        reflash(row, list, mine_Arr, show_Arr);
        //更新表后分两种情况 一种为游戏结束，另一种为游戏继续
        sucess(mine_Arr, show_Arr);
    }
}
//点击后更新表
void reflash(int row,int list,int mine_Arr[degree+2][degree+2],Table show_Arr[degree+2][degree+2]){
    //位置不合法
    if (row<1||row>degree||list<1||list>degree) {
        return;
    }
    int num_Around=checkAround(row, list, mine_Arr,show_Arr);//周围雷数
    //如果如果当前位置周围雷数为0且之前未进行过该操作
    if (num_Around==0&&show_Arr[row][list].state!=' ') {
        for (int i=row-1; i<=row+1; i++) {
            for (int j=list-1; j<=list+1; j++) {
                if (i!=row||j!=list) {
                    //当前点的周围8个格
                    if(show_Arr[i][j].state=='*')
                        show_Arr[i][j].state='+';//将之前未检测过的周围点设置为'+'
                }//if
                else{
                    show_Arr[i][j].state=' ';//当前点设置为' '
                }//else
                //将周围雷为0的位置连续检测更新
//                showCur(mine_Arr, show_Arr, 0);测试代码
                reflash(i, j, mine_Arr, show_Arr);
            }//for
        }//for
    }//if
    else{
        //周围有雷或者之前进行过操作
        show_Arr[row][list].id=num_Around;
        show_Arr[row][list].state=' ';//保持一致
    }
}
//检查是否排雷成功
void sucess(int mine_Arr[degree+2][degree+2],Table show_Arr[degree+2][degree+2]){
//    测试代码
//    showText(show_Arr);
    int blankNum=0;
    for (int i=1; i<degree+1; i++) {
        for (int j=1; j<degree+1; j++) {
            //当前位置状态为*或F
            if (show_Arr[i][j].state=='*'||show_Arr[i][j].state=='F') {
                blankNum++;
            }
        }
    }
    //*或F和为总雷数
    if (blankNum==num_Mine) {
        live=0;
        end=clock();
        double duration =(double)(end-begin);
        printf("YOU WIN,TOTAL TIME IS %.0lfms\n",duration*1000);
    }
}
//检查当前位置是否有雷 1是雷/0不是
int checkMine(int row,int list,int mine_Arr[degree+2][degree+2]){
    if (mine_Arr[row][list]==1) {
        return 1;
    }else
    {
    return 0;
    }
}
//检查周边九宫格里面是否有雷,返回雷的个数
int checkAround(int row,int list,int mine_Arr[degree+2][degree+2],Table show_Arr[degree+2][degree+2]){
    num_Flag =0;//周围的小红旗数
    num_MineLift=0;//周围没有被小红旗覆盖位置的雷数
    int num_Around=0;//周围实际的雷数
    for (int i=row-1; i<=row+1; i++) {
        for (int j=list-1; j<=list+1; j++) {
            //统计实际雷数
            if (mine_Arr[i][j]==1) {
                num_Around++;
            }
            //统计红旗的数量
            if (show_Arr[i][j].state=='F') {
                num_Flag++;
            }
            //统计周围小红旗没有覆盖位置，剩余的雷数
            if (mine_Arr[i][j]==1&&show_Arr[i][j].state!='F') {
                num_MineLift++;
            }
        }//for
    }//for
    return num_Around;
}
//放置小红旗
void setFlag(int row,int list,Table show_Arr[degree+2][degree+2]){
    if (show_Arr[row][list].state=='*') {
        show_Arr[row][list].state='F';
    }else{
        printf("非法操作\n");
    }
}
//撤掉小红旗
void removeFlag(int row,int list,Table show_Arr[degree+2][degree+2]){
    if (show_Arr[row][list].state=='F') {
        show_Arr[row][list].state='*';
    }else{
        printf("非法操作\n");
    }
}
//检查当前位置是否能显示出一片无雷的区域
void checkAround_F(int row,int list,int mine_Arr[degree+2][degree+2],Table show_Arr[degree+2][degree+2]){
    checkAround(row, list, mine_Arr, show_Arr);//更新周围小红旗数、雷数
    //如果位置合法
    if (show_Arr[row][list].id>0&&num_Flag>0) {
        //雷都被小红旗覆盖
        if (num_MineLift==0) {
            if (show_Arr[row][list].id==num_Flag) {
                //如果当前位置周围雷数等于周围小红旗数，则更新其余位置的信息
                for (int i=row-1; i<=row+1; i++) {
                    for (int j=list-1; j<=list+1; j++) {
                        //如果当前位置状态为*,则更新id
                        if (show_Arr[i][j].state=='*') {
                            //该位置更新后的id肯定不为0
                            show_Arr[i][j].id=checkAround(i, j, mine_Arr, show_Arr);
                            show_Arr[i][j].state=' ';//保持一致
                        }//if
                    }//for
                }//for
                //判断是否排雷成功
                sucess(mine_Arr, show_Arr);
            }//if--else 如果红旗数多于地雷数，操作无效
            else{
                printf("无效操作\n");
            }
        }//if--else: //雷未都被小红旗覆盖
        else {
            Mine_Exist=1;
            live=0;
            printf("YOU LOSE\n");
        }
    }//if--else:位置不合法
    else{
        printf("非法操作\n");
    }
}
//向用户展示最新情况
void showCur(int mine_Arr[degree+2][degree+2],Table show_Arr[degree+2][degree+2],int Mine_Exist){
    //点击的位置为雷，失败，显示完整雷分布
    if (Mine_Exist==1) {
        for (int i=0; i<degree+1; i++) {
            for (int j=0; j<degree+1; j++) {
                if (i==0||j==0) {
                    printf("%4d",show_Arr[i][j].id);
                }else{
                    printf("%4d",mine_Arr[i][j]);
                }
            }//for
            printf("\n");
            printf("\n");
        }
    }//if
    else{
        //点击的位置不为雷
    for (int i=0; i<degree+1; i++) {
        for (int j=0; j<degree+1; j++) {
            //分两种情况，周围有地雷显示数目，没有地雷显示状态
                if (show_Arr[i][j].id) {
                    printf("%4d",show_Arr[i][j].id);
                }else{
                printf("%4c",show_Arr[i][j].state);
                }
        }//for
        printf("\n");
        printf("\n");
        
    }//for
    }//else
}
void newGame(){
    int num=0;
    while (num!=1&&num!=2) {
        printf("是否要重新开始？1.是 2.否\n");
        scanf("%d",&num);
        inputError();
    }
    switch (num) {
        case 1:
            help();
            break;
        default:
            live=0;
            break;
    }
}
void inputError(){
    //如果用户输入不合法，提取缓存区的值，防止死循环
    char c;
    do{scanf("%c",&c);}while(c !='\n');
}
